// objectmanager.cpp : contains the definition of the object managing class, which can combine components
// with other aspects (scripts) to create specific "objects"
//$Id: ObjectManager.cpp 25 2009-08-21 02:02:10Z brwarner2 $

#include "StdAfx.h"
#include "ObjectManager.h"
#include "ComponentManager.h"
#include "GameMonkey.h"
#include "Messages.h"
#include "Entity.h"
#include "Area.h"

CObjectManager::CObjectManager(CComponentManager* pCom, gmMachine* pMac) : m_pGM(pMac), m_pCManager(pCom)
{
}

CObjectManager::~CObjectManager(void)
{
	Release();
	m_pGM->CollectGarbage(true);
}

ObjectID CObjectManager::FindByName( const std::string& name )
{
	//For each object registered
	ObjectList::iterator iter = m_objects.begin();
	for(; iter != m_objects.end(); iter++)
	{
		CEntity* pEnt = dynamic_cast<CEntity*>( *(*iter)->components.begin() );
		if( pEnt->WorldName() == name ) return pEnt->GetObjectID();
	}
	return -1;
}

void CObjectManager::SetArea( ObjectID obj, CArea* pArea )
{
	//Get that object
	Object* pObj = 0;
	try {
		pObj = m_objects[obj];
	}catch( const std::exception& ) {
		return;
	}
	if(pObj == 0) return;

	//For each component
	std::list<IComponent*>::iterator iter = pObj->components.begin();
	for(; iter != pObj->components.end(); iter++)
	{
		(*iter)->m_pArea = pArea;
	}
}

void CObjectManager::RegisterGM( void )
{
	//Register us as the global object table
	SetGlobalObjectManager(*m_pGM, this);

	//Register GM functions
	RegisterGlobalGM(*m_pGM);

	//Tell the component manager to register its GM
	m_pCManager->RegisterGM(m_pGM);
}

void CObjectManager::LoadObjectData( const std::string& filename )
{
	//Get the globals table
	gmTableObject *globals = m_pGM->GetGlobals();

	//Add an OBJ table
	gmVariable obj( m_pGM->AllocTableObject() );
	gmTableObject* table = obj.GetTableObjectSafe();
	table->Set(m_pGM, "components", gmVariable(m_pGM->AllocTableObject()));
	table->Set(m_pGM, "hooks", gmVariable(m_pGM->AllocTableObject()));
	globals->Set(m_pGM, "OBJECT", obj);

	//Register the object declaration functions
	ExposeObjectDeclarators(*m_pGM, table);

	//Load the script
	FromFile(*m_pGM, filename.c_str());

	//Get the global object
	obj = globals->Get(m_pGM, "OBJECT");
	table = obj.GetTableObjectSafe();
	if(!table) return;

	//Add to global array
	using std::string;
	string::size_type slash = filename.find_last_of('\\');
	string filestring;
	if( slash != string::npos ) filestring = filename.substr(slash+1);
	else filestring = filename;
	filestring = filestring.substr(0, filestring.find_last_of('.'));
	m_objecttables[filestring] = table->Duplicate(m_pGM);
	m_pGM->AddCPPOwnedGMObject(m_objecttables[filestring]);

	//Delete old object table
	globals->Set(m_pGM, "OBJECT", gmVariable::s_null);

	//Hide object declaration functions
	HideObjectDeclarators(*m_pGM, table);

	//Clean up
	m_pGM->CollectGarbage(true);
}

void CObjectManager::CreateMembers( ComponentID componentID, gmTableObject* pTable )
{
	RegistryList::iterator iter = m_members.begin();
	for(; iter != m_members.end(); iter++)
	{
		//Check if it has same component
		if( iter->second.componentID == componentID )
			//Add member
			pTable->Set( m_pGM, iter->second.name.c_str(), gmVariable(iter->second.func) );
	}
}

ObjectID CObjectManager::CreateObject(const std::string &id, const ParameterList &params, CArea* pArea)
{
	//Check if that id exists
	if( m_objecttables.find(id) == m_objecttables.end() ) return -1;

	//Get the object table
	gmTableObject *objtable = m_objecttables[id];

	//Create a new object
	Object* pObj = new Object();

	//Get the component list
	gmTableObject *clist = objtable->Get(m_pGM, "components").GetTableObjectSafe();
	if(clist == 0)
	{
		delete pObj;
		return -1;
	}

	//Add to the object list
	m_objects.push_back(pObj);
	ObjectID oid = m_objects.size()-1;

	//Setup table
	pObj->gmTable = objtable->Duplicate(m_pGM);
	m_pGM->AddCPPOwnedGMObject(pObj->gmTable);
	pObj->gmTable->Set(m_pGM, "components", gmVariable::s_null);
	pObj->gmTable->Set(m_pGM, "ObjectID", gmVariable((int)oid));

	//Create an entity component
	CEntity* pEntity = dynamic_cast<CEntity*>(m_pCManager->Create(CEntity::COMPONENT_ID));
	pEntity->m_objectID = oid;
	pEntity->m_pEntity = pEntity;
	pEntity->m_pOManager = this;
	pEntity->m_pGM = m_pGM;
	pEntity->m_pArea = pArea;
	pEntity->Init(params);
	pObj->components.push_back(pEntity);
	CreateMembers(CEntity::COMPONENT_ID, pObj->gmTable);

	//For each component ID in the list
	gmTableIterator iter;
	for(gmTableNode* node = clist->GetFirst(iter); node != 0; node = clist->GetNext(iter))
	{
		//Get component ID
		ComponentID cid = node->m_value.GetIntSafe();

		//Check if it exists
		if( GetComponent(oid, cid) != 0 ) continue;
		
		//Create component
		IComponent* pComponent = m_pCManager->Create(cid);

		//Add to the object
		if(pComponent == 0) continue;
		pObj->components.push_back(pComponent);

		//Set object ID
		pComponent->m_objectID = oid;

		//Set entity
		pComponent->m_pEntity = pEntity;

		//Set manager
		pComponent->m_pOManager = this;

		//Set GM
		pComponent->m_pGM = m_pGM;

		//Set area
		pComponent->m_pArea = pArea;

		//Initialize component
		pComponent->Init(params);

		//Add members
		CreateMembers(cid, pObj->gmTable);
	}

	//Send the startup message
	Message(oid, Messages::Startup);

	//Return object ID
	return oid;
}

void CObjectManager::PushObject( gmThread* a_thread, ObjectID objectID )
{
	//Get the table
	if( objectID == -1 ) a_thread->PushNull();
	else a_thread->PushTable(m_objects[objectID]->gmTable);
}

void CObjectManager::Message( ObjectID obj, MessageID msg, const boost::any& arg )
{
	//Get that object
	Object* pObj = 0;
	try {
		pObj = m_objects[obj];
	}catch( const std::exception& ) {
		return;
	}
	if(pObj == 0) return;

	//For each component
	std::list<IComponent*>::iterator iter = pObj->components.begin();
	for(; iter != pObj->components.end(); iter++)
	{
		(*iter)->Message(msg, arg);
	}
}

IComponent* CObjectManager::GetComponent( ObjectID objectID, ComponentID compID )
{
	//Get that object
	Object* pObj = 0;
	try {
		pObj = m_objects[objectID];
	}catch( const std::exception& ) {
		return 0;
	}
	if(pObj == 0) return 0;

	//Find the component
	std::list<IComponent*>::iterator iter = pObj->components.begin();
	for(; iter != pObj->components.end(); iter++)
	{
		//Return the one of the right ID
		if( (*iter)->GetComponentID() == compID ) return (*iter);
	}
	return 0;
}

void CObjectManager::CallHook(ObjectID objectID, const std::string &name,
							  gmVariable arg1, gmVariable arg2, gmVariable arg3, gmVariable arg4)
{
	//Get that object
	Object* pObj = 0;
	try {
		pObj = m_objects[objectID];
	}catch( const std::exception& ) {
		return;
	}
	if(pObj == 0) return;

	//Get the object hooks table
	gmTableObject* hooktable = pObj->gmTable->Get(m_pGM, "hooks").GetTableObjectSafe();

	//Get the specific hook table
	gmVariable innerhooktable = hooktable->Get(m_pGM, name.c_str());
	if( innerhooktable.IsNull() ) return;
	gmTableObject* hooks = innerhooktable.GetTableObjectSafe();
	if(hooks == 0) return;

	//TODO: Add order to the table
	gmTableIterator iter;
	gmTableNode* node = hooks->GetFirst(iter);
	for(; node != 0; node = hooks->GetNext(iter))
	{
		//Call the function
		gmCall call;
		if( call.BeginFunction( m_pGM, node->m_value.GetFunctionObjectSafe(),
			gmVariable(pObj->gmTable) ) ) {
				//Push arguments if they exist
#define ARG(N) if( !arg##N.IsNull()) call.AddParam(arg##N);
				ARG(1)ARG(2)ARG(3)ARG(4)
#undef ARG
				call.End();
				PrintErrorLog(*m_pGM);
		}
	}
}

int GM_CDECL CObjectManager::MemberCallback( gmThread* a_thread )
{
	//Get the machine
	gmMachine* pGM = a_thread->GetMachine();

	//Get the function object
	const gmFunctionObject *me = a_thread->GetFunctionObject();

	//Look it up in the registry
	RegisteredMember member = GetGlobalObjectManager(*pGM)->m_members[me];

	//Get the object
	const gmVariable* _this = a_thread->GetThis();
	if(const_cast<gmVariable*>(_this)->IsNull()) return GM_EXCEPTION;
	gmTableObject *_thistable = _this->GetTableObjectSafe();
	if(!_thistable) return GM_EXCEPTION;

	//Get our object ID
	ObjectID objectID = (ObjectID)_thistable->Get(pGM, "ObjectID").GetIntSafe();

	//Get the component
	IComponent* pComponent = GetGlobalObjectManager(*pGM)->GetComponent(objectID, member.componentID);
	if(!pComponent) return GM_EXCEPTION;

	//Run the member
	return pComponent->GMCall( a_thread, member.name );
}

void CObjectManager::RegisterMemberFunction(ComponentID componentID, const std::string &name)
{
	//Create a new function object pointing to the member callback
	gmFunctionObject* pFunc = m_pGM->AllocFunctionObject(MemberCallback);

	//Get the information into the registry
	RegisteredMember member = { name, componentID, pFunc };
	m_members[pFunc] = member;

	//Make it a global
	//m_pGM->GetGlobals()->Set(m_pGM, name.c_str(), gmVariable(pFunc));
	m_pGM->AddCPPOwnedGMObject(pFunc);
}

void CObjectManager::Destroy( ObjectID objectID )
{
	//Get that object
	Object* pObj = 0;
	try {
		pObj = m_objects[objectID];
	}catch( const std::exception& ) {
		return;
	}
	if(pObj == 0) return;

	//Erase each component
	std::list<IComponent*>::iterator iter = pObj->components.begin();
	for(; iter != pObj->components.end(); iter++)
	{
		m_pCManager->Destroy( (*iter) );
	}

	//Remove the table of the entity
	m_pGM->RemoveCPPOwnedGMObject(pObj->gmTable);
	m_pGM->CollectGarbage(true);

	//Erase it from the list
	delete pObj;
	pObj = m_objects[objectID] = 0;
}

void CObjectManager::Release(void)
{
	//Clear the objects
	for( unsigned int i = 0; i < m_objects.size(); i++ )
	{
		Destroy(i);
	}
	m_objects.clear();

	//Clear registered members
	RegistryList::iterator iter1 = m_members.begin();
	for(; iter1 != m_members.end(); iter1++)
	{
		m_pGM->RemoveCPPOwnedGMObject(iter1->second.func);
	}
	m_members.clear();

	//Clear registered object types
	std::map<std::string, gmTableObject*>::iterator iter = m_objecttables.begin();
	for(; iter != m_objecttables.end(); iter++)
	{
		m_pGM->RemoveCPPOwnedGMObject(iter->second);
	}
	m_objecttables.clear();
}

gmTableObject* CObjectManager::GetObjectTable( ObjectID objectID )
{
	//Get the table
	if( objectID == -1 ) return 0;
	return m_objects[objectID]->gmTable;
}

void CObjectManager::Broadcast( MessageID msg, const boost::any& arg )
{
	ObjectList::iterator iter = m_objects.begin();
	ObjectID oid = 0;
	for(; iter != m_objects.end(); iter++)
	{
		if( *iter == 0 ) continue;
		Message(oid, msg, arg);
		oid++;
	}
}